﻿using System;
using System.Collections.Generic;
using System.Text;

namespace IceDog.DNL.CSharp.Grammar.FormatProvider
{
    /// <summary>
    /// AnyRadix 类支持“Ra”格式设置代码，并将 Int64 值转换为介于 2 到 36 之间的任何指定基数的字符串。
    /// </summary>
    public class AnyRadix:ICustomFormatter,IFormatProvider
    {
        // The value to be formatted is returned as a signed string 
        // of digits from the rDigits array. 
        const string radixCode = "Ra";
        private static char[] rDigits = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y', 'Z' };

        /// <summary>Converts the value of a specified object to an equivalent string representation using specified format and culture-specific formatting information.</summary>
        /// <param name="format">A format string containing formatting specifications.</param>
        /// <param name="arg">An object to format.</param>
        /// <param name="formatProvider">An object that supplies format information about the current instance.</param>
        /// <returns>The string representation of the value of <paramref name="arg">arg</paramref>, formatted as specified by <paramref name="format">format</paramref> and <paramref name="formatProvider">formatProvider</paramref>.</returns>
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            // If no format string is provided or the format string cannot 
            // be handled, use IFormattable or standard string processing.
            if (format == null ||!format.Trim().StartsWith(radixCode))
            {
                if (arg is IFormattable)
                    return ((IFormattable)arg).
                        ToString(format, formatProvider);
                else
                    return arg.ToString();
            }

            // The formatting is handled here.
            int digitIndex = 0;
            long radix;
            long longToBeFormatted;
            long longPositive;
            char[] outDigits = new char[63];

            // Extract the radix from the format string.
            format = format.Replace(radixCode, "");
            try
            {
                radix = Convert.ToInt64(format);
            }
            catch (Exception ex)
            {
                throw new ArgumentException(String.Format(
                    "The radix \"{0}\" is invalid.",
                    format), ex);
            }

            // Verify that the radix is in the proper range.
            if (radix < 2 || radix > 36)
                throw new ArgumentException(String.Format(
                    "The radix \"{0}\" is not in the range 2 - 36.",
                    format));

            // Verify that the argument can be converted to a long integer.
            try
            {
                longToBeFormatted = (long)arg;
            }
            catch (Exception ex)
            {
                throw new ArgumentException(String.Format(
                    "The argument \"{0}\" cannot be " +
                    "converted to an integer value.",
                    arg), ex);
            }

            // Extract the magnitude for conversion.
            longPositive = Math.Abs(longToBeFormatted);

            // Convert the magnitude to a digit string.
            for (digitIndex = 0; digitIndex <= 64; digitIndex++)
            {
                if (longPositive == 0) break;

                outDigits[outDigits.Length - digitIndex - 1] =
                    rDigits[longPositive % radix];
                longPositive /= radix;
            }

            // Add a minus sign if the argument is negative.
            if (longToBeFormatted < 0)
                outDigits[outDigits.Length - digitIndex++ - 1] =
                    '-';

            return new string(outDigits,
                outDigits.Length - digitIndex, digitIndex);
        }

        /// <summary>Returns an object that provides formatting services for the specified type.</summary>
        /// <param name="formatType">An object that specifies the type of format object to return.</param>
        /// <returns>An instance of the object specified by <paramref name="formatType">formatType</paramref>, if the <see cref="T:System.IFormatProvider"></see> implementation can supply that type of object; otherwise, null.</returns>
        public object GetFormat(Type formatType)
        {
            // Here, the same object (this) is returned, but it would 
            // be possible to return an object of a different type.
            if (formatType == typeof(ICustomFormatter))
                return this;
            else
                return null;
        }
    }
}
